#include <osg/MatrixTransform>
#include <osg/ShapeDrawable>
#include <osgViewer/Viewer>
#include <osgViewer/ViewerEventHandlers>
#include <osg/Geode>
#include <osg/TexGen>
#include <osg/PositionAttitudeTransform>
#include <osgUtil/Optimizer>

#include <osg/Texture>
#include <osg/Node>
#include <osgDB/DatabasePager>
#include <osgDB/ReadFile>
#include <osgUtil/PrintVisitor>

#include "SkyBox.h"
#include "Brunnen.h"
#include "MyEventHandler.h"
#include "SceneGraphHandler.h"
#include "Hud.h"
#include "TextBox.h"
#include "Wasser.h"
#include "Gelaende.h"
#include "Haus.h"
#include "UpdateScenePosCallback.h"

using namespace osg;
using namespace osgViewer;
using namespace std;
using namespace osgDB;

#ifndef PI
#define PI    3.14159265358979323846f
#endif

#define RESOURCES 26
string res[RESOURCES];

/** Objekte **/
Gelaende*					terrainOBJ;
Wasser*						waterOBJ;
Brunnen*					brunnenOBJ;
Haus*						hausOBJ;
ref_ptr<Node>				baum3DS;
ref_ptr<Node>				cowOBJ;
/** Gruppen **/
ref_ptr<Group>				terrainGrp;
ref_ptr<Group>				hausGrp;
ref_ptr<Group>				brunnenGrp;
ref_ptr<Group>				baumGrp;
/** Transform **/
ref_ptr<MatrixTransform>	terrainTransform;
ref_ptr<MatrixTransform>	hausTransform;
ref_ptr<MatrixTransform>	brunnenTransform;
ref_ptr<MatrixTransform>	baumTransform;
ref_ptr<MatrixTransform>	kuhTransform;
ref_ptr<MatrixTransform>	mainTransform;
/** Lichtquellen **/
ref_ptr<Light>				sonne;
ref_ptr<Light>				sonne2;
ref_ptr<Light>				mond;
ref_ptr<Light>				wohnzimmer;
ref_ptr<Light>				kueche;
ref_ptr<Light>				schlafzimmer;
ref_ptr<Light>				flur;
ref_ptr<Light>				bad;
/** Handler **/
SceneGraphHandler*			sceneHandler;
MyEventHandler*				eventHandler;
/** Einstellungen **/
int							detail;
int							hoehe;
int							breite;
bool						fenster;

/** Methode zum automatischen erstellen des Pfades.
	Wird Unterschieden nach Betriebssystem **/
void initResources(){
	#ifdef __linux__
		string path = "Gelaende/Modelle/";
	#elif _WIN32
		string path = "Modelle\\";
	#endif

		for(int i = 0; i < RESOURCES; i++){
			res[i]+=path;
		}

		res[0]+="Water.obj";
		res[1]+="haus.obj";
		res[2]+="Terrain.obj";
		res[3]+="Stein.obj";
		res[4]+="Stein2.obj";
		res[5]+="Stein3.obj";
		res[6]+="Stein4.obj";
		res[7]+="schilf2.obj";
		res[8]+="schilfcords.ini";
		res[9]+="negx.jpg";
		res[10]+="negy.jpg";
		res[11]+="negz.jpg";
		res[12]+="posx.jpg";
		res[13]+="posy.jpg";
		res[14]+="posz.jpg";
		res[15]+="TexturBrunn.jpg";
		res[16]+="MetalBare1.jpg";
		res[17]+="MetalBare2.jpg";
		res[18]+="negxNacht.jpg";
		res[19]+="negyNacht.jpg";
		res[20]+="negzNacht.jpg";
		res[21]+="posxNacht.jpg";
		res[22]+="posyNacht.jpg";
		res[23]+="poszNacht.jpg";
		res[24]+="baum.obj";
		res[25]+="cow.osg";
}

/** Methode zum Erstellen der Objekte **/
void erstelleObjecte(){

	cout << "Erstelle Szene. Bitte Warten." << endl;
	/** Gelaende erstellen **/
	terrainOBJ = new Gelaende( res, 2, detail );
	/** Haus erstellen **/
	hausOBJ = new Haus( &res[1] );
	/** Brunnen erstellen **/
	brunnenOBJ = new Brunnen( res, 15 );
	/** Baum **/
	baum3DS = osgDB::readNodeFile( res[24] );
	/** Wasser Objekt erstellen **/
	waterOBJ = new Wasser( &res[0] );
	waterOBJ->setReflexion();

	/** Nebel erstellen **/
    sceneHandler->fog->setMode( Fog::LINEAR );
    sceneHandler->fog->setStart( 0.0f );
    sceneHandler->fog->setEnd( 800.0f );
    sceneHandler->fog->setColor( Vec4(1.0f, 1.0f, 1.0f, 1.0f) );
}

/** Entsprechende Gruppen erstellen **/
void erstelleGruppen(){
	
	terrainGrp = new Group;
	terrainGrp->addChild( terrainOBJ->getGelaende() );
	terrainGrp->addChild( waterOBJ->getWasser()->get() );

	hausGrp = new Group;
    hausGrp->addChild( hausOBJ->getHaus()->get() );

	brunnenGrp = new Group;
    brunnenGrp->addChild( brunnenOBJ->get() );

	baumGrp = new Group;
    baumGrp->addChild( baum3DS );

	mainTransform = new MatrixTransform;

	cowOBJ = readNodeFile( res[25] );

}

/** Transformieren der Gruppen **/
void transformiereGruppen(){

	terrainTransform = new MatrixTransform;
	terrainTransform->setMatrix( Matrix::rotate( -(PI/2), Vec3(0.0f, 0.0f, 1.0f) ) );
	terrainTransform->addChild( terrainGrp.get() );

	hausTransform = new MatrixTransform;
	hausTransform->setMatrix( Matrix::translate( 0.0f, 8.0f, 0.1f ) * Matrix::scale( 3.0f, 3.0f, 3.0f) );
	hausTransform->addChild( hausGrp.get() );

	brunnenTransform = new MatrixTransform;
	brunnenTransform->setMatrix(Matrix::scale(0.1f, 0.1f, 0.1f) * Matrix::translate(25.0f, 8.0f, 2.93f));
	brunnenTransform->getOrCreateStateSet()->setMode(GL_RESCALE_NORMAL,StateAttribute::ON);
	brunnenTransform->addChild( brunnenGrp.get() );

	kuhTransform = new MatrixTransform;
	kuhTransform->setMatrix( Matrix::translate( 10.0f, 0.0f, 10.0f ) );
	kuhTransform->addChild( cowOBJ.get() );

	baumTransform = new MatrixTransform;
	baumTransform->setMatrix(Matrix::scale(2.0f, 2.0f, 2.0f) * Matrix::translate( 0.0f, 0.0f, 1.0f ));
	baumTransform->addChild( baumGrp.get() );
}

/** Licht der Szene erstellen **/
void erstelleLicht(){

	/** Sonne **/
	sonne = new Light;
	sonne->setLightNum(0);
	sonne->setPosition( Vec4( 200.0, 100.0, 200.0, 1.0 ) );
	sonne->setDiffuse( Vec4( 10.0, 10.0, 10.0, 1.0 ) );
	sonne->setSpecular( Vec4( 0.8, 0.8, 0.8, 1.0 ) );
    sonne->setAmbient( Vec4( 10.0, 10.0, 10.0, 1.0 ) );

	sceneHandler->lichtSonne->setLight( sonne );

	sonne2 = new Light;
	sonne2->setLightNum(1);
	sonne2->setPosition( Vec4( -200.0, -100.0, 200.0, 1.0 ) );
	sonne2->setDiffuse( Vec4( 1.0, 1.0, 1.0, 1.0 ) );
	sonne2->setSpecular( Vec4( 0.8, 0.8, 0.8, 1.0 ) );
    sonne2->setAmbient( Vec4( 1.0, 1.0, 1.0, 1.0 ) );

	sceneHandler->lichtSonne2->setLight( sonne2 );

	/** Mond **/
	mond = new Light;
	mond->setLightNum(2);
	mond->setPosition( Vec4( 200.0, 100.0, 200.0, 1.0 ) );
	mond->setDiffuse( Vec4( 0.0, 0.2, 0.8, 1.0 ) );
	mond->setSpecular( Vec4( 1.0, 1.0, 1.0, 1.0 ) );
    mond->setAmbient( Vec4( 0.0, 0.2, 0.8, 1.0 ) );

	sceneHandler->lichtMond->setLight( mond );

	/** Licht im Haus **/
	bad = new Light;
	bad->setLightNum(3);
	bad->setPosition( Vec4( 0, 15.88, 14.3, 1.0 ) );

	sceneHandler->lichtBad->setLight( bad );

	wohnzimmer = new Light;
	wohnzimmer->setLightNum(4);
	wohnzimmer->setPosition( Vec4( -1.88, 19.78, 6.8, 1.0 ) );

	sceneHandler->lichtWohnzimmer->setLight( wohnzimmer );

	kueche = new Light;
	kueche->setLightNum(5);
	kueche->setPosition( Vec4( 0, 31, 6.8, 1.0 ) );

	sceneHandler->lichtSchlafzimmer->setLight( kueche );

	schlafzimmer = new Light;
	schlafzimmer->setLightNum(6);
	schlafzimmer->setPosition( Vec4( -4.15, 27.5, 14.3, 1.0 ) );

	sceneHandler->lichtKueche->setLight( schlafzimmer );

	flur = new Light;
	flur->setLightNum(7);
	flur->setPosition( Vec4( 4.15, 27.5, 14.3, 1.0 ) );

	sceneHandler->lichtFlur->setLight( flur );
}

/** SkyBox erstellen **/
void erstelleSkyBox(){

	ref_ptr<Geode> skyGeode = new Geode;
    skyGeode->addDrawable( new ShapeDrawable(new Sphere(Vec3(), sceneHandler->lightGroup->getBound().radius())) );
	skyGeode->setCullingActive( false );
    
	/** sky Box Tag **/
    ref_ptr<SkyBox> skyboxTag = new SkyBox;
    skyboxTag->getOrCreateStateSet()->setTextureAttributeAndModes( 0, new TexGen );
    skyboxTag->setEnvironmentMap( 0,
        osgDB::readImageFile(res[12]), osgDB::readImageFile(res[9]),
        osgDB::readImageFile(res[13]), osgDB::readImageFile(res[10]),
        osgDB::readImageFile(res[14]), osgDB::readImageFile(res[11]) );
    skyboxTag->addChild( skyGeode.get() );

	sceneHandler->skyBoxTagTransform->setMatrix(Matrix::rotate(-1.5f,X_AXIS,0.0f,Y_AXIS,0.0f,Z_AXIS));
	sceneHandler->skyBoxTagTransform->addChild(skyboxTag.get());

	/** sky Box Nacht **/
    ref_ptr<SkyBox> skyboxNacht = new SkyBox;
    skyboxNacht->getOrCreateStateSet()->setTextureAttributeAndModes( 0, new TexGen );
    skyboxNacht->setEnvironmentMap( 0,
        osgDB::readImageFile(res[21]), osgDB::readImageFile(res[18]),
        osgDB::readImageFile(res[22]), osgDB::readImageFile(res[19]),
        osgDB::readImageFile(res[23]), osgDB::readImageFile(res[20]) );
    skyboxNacht->addChild( skyGeode.get() );

	sceneHandler->skyBoxNachtTransform->setMatrix(Matrix::rotate(-1.5f,X_AXIS,0.0f,Y_AXIS,0.0f,Z_AXIS));
	sceneHandler->skyBoxNachtTransform->addChild(skyboxNacht.get());
}

/** Allgemeine Einstellungen fuer den Programmstart **/
void startEinstellungen(){

	/** Festlegen der Detail Einstellungen **/
	detail = -1;
	while(detail<0 || detail >9){
		cout << "Bitte Detail Level einstellen (0 - 9): ";
		cin >> detail;
	}
	
	/** Festlegen ob Venster oder Vollbild **/
	fenster = true;
	string vergF = "F";
	string vergf = "f";
	string vergV = "V";
	string vergv = "v";
	string eing;
	bool bfenster = false;
	while(!bfenster){
		cout << "Im <F>enster oder <V>ollbild Modus starten: ";
		cin >> eing;
		if((vergF.compare(eing)==0) || (vergf.compare(eing)==0)){
			fenster = true;
			bfenster = true;
		} else if((vergV.compare(eing)==0) || (vergv.compare(eing)==0)) {
			fenster = false;
			bfenster = true;
		} else {
			bfenster = false;
		}
	}

	/** Festlegen der Fenstergroesse **/
	if(fenster){
		breite = hoehe = 0;
		while((breite * hoehe) ==0){
		cout << "Breite: ";
		cin >> breite;
		cout << "Hoehe:  ";
		cin >> hoehe;
		}
	}

}

void erstelleBaeume(){
	ref_ptr<MatrixTransform> baum1Transform = new MatrixTransform;
	baum1Transform->setMatrix(Matrix::translate( 0.0f, -20.0f, 0.0f ));
	baum1Transform->addChild( baumTransform.get() );

	ref_ptr<MatrixTransform> baum2Transform = new MatrixTransform;
	baum2Transform->setMatrix(Matrix::translate( 25.0f, -25.0f, 0.0f ));
	baum2Transform->addChild( baumTransform.get() );

	ref_ptr<MatrixTransform> baum3Transform = new MatrixTransform;
	baum3Transform->setMatrix(Matrix::translate( 12.0f, -32.0f, 0.0f ));
	baum3Transform->addChild( baumTransform.get() );

	ref_ptr<MatrixTransform> baum4Transform = new MatrixTransform;
	baum4Transform->setMatrix(Matrix::translate( -100.0f, 50.0f, 0.0f ));
	baum4Transform->addChild( baumTransform.get() );

	ref_ptr<MatrixTransform> baum5Transform = new MatrixTransform;
	baum5Transform->setMatrix(Matrix::translate( -100.0f, 60.0f, 6.0f ));
	baum5Transform->addChild( baumTransform.get() );

	ref_ptr<MatrixTransform> baum6Transform = new MatrixTransform;
	baum6Transform->setMatrix(Matrix::translate( -110.0f, 40.0f, 0.0f ));
	baum6Transform->addChild( baumTransform.get() );

	ref_ptr<MatrixTransform> baum7Transform = new MatrixTransform;
	baum7Transform->setMatrix(Matrix::translate( 100.0f, -100.0f, 0.0f ));
	baum7Transform->addChild( baumTransform.get() );

	ref_ptr<MatrixTransform> baum8Transform = new MatrixTransform;
	baum8Transform->setMatrix(Matrix::translate( 110.0f, -90.0f, 0.0f ));
	baum8Transform->addChild( baumTransform.get() );

	ref_ptr<MatrixTransform> baum9Transform = new MatrixTransform;
	baum9Transform->setMatrix(Matrix::translate( 130.0f, -80.0f, 0.0f ));
	baum9Transform->addChild( baumTransform.get() );

	ref_ptr<MatrixTransform> baum10Transform = new MatrixTransform;
	baum10Transform->setMatrix(Matrix::translate( 30.0f, 33.0f, 0.0f ));
	baum10Transform->addChild( baumTransform.get() );

	ref_ptr<MatrixTransform> baum11Transform = new MatrixTransform;
	baum11Transform->setMatrix(Matrix::translate( 55.0f, -48.0f, 0.0f ));
	baum11Transform->addChild( baumTransform.get() );

	ref_ptr<MatrixTransform> baum12Transform = new MatrixTransform;
	baum12Transform->setMatrix(Matrix::translate( 42.0f, -42.0f, 0.0f ));
	baum12Transform->addChild( baumTransform.get() );

	ref_ptr<MatrixTransform> baum13Transform = new MatrixTransform;
	baum13Transform->setMatrix(Matrix::translate( 60.0f, -65.0f, 0.0f ));
	baum13Transform->addChild( baumTransform.get() );

	ref_ptr<MatrixTransform> baum14Transform = new MatrixTransform;
	baum14Transform->setMatrix(Matrix::translate( 38.0f, -22.0f, 0.0f ));
	baum14Transform->addChild( baumTransform.get() );

	ref_ptr<MatrixTransform> baum15Transform = new MatrixTransform;
	baum15Transform->setMatrix(Matrix::translate( -110.0f, -37.0f, 0.0f ));
	baum15Transform->addChild( baumTransform.get() );

	ref_ptr<MatrixTransform> baum16Transform = new MatrixTransform;
	baum16Transform->setMatrix(Matrix::translate( -122.0f, -48.0f, 0.0f ));
	baum16Transform->addChild( baumTransform.get() );

	ref_ptr<MatrixTransform> baum17Transform = new MatrixTransform;
	baum17Transform->setMatrix(Matrix::translate( -115.0f, -55.0f, 0.0f ));
	baum17Transform->addChild( baumTransform.get() );

	ref_ptr<MatrixTransform> baum18Transform = new MatrixTransform;
	baum18Transform->setMatrix(Matrix::translate( -123.0f, -27.0f, 0.0f ));
	baum18Transform->addChild( baumTransform.get() );

	ref_ptr<MatrixTransform> baum19Transform = new MatrixTransform;
	baum19Transform->setMatrix(Matrix::translate( -125.0f, -70.0f, 0.0f ));
	baum19Transform->addChild( baumTransform.get() );

	ref_ptr<MatrixTransform> baum20Transform = new MatrixTransform;
	baum20Transform->setMatrix(Matrix::translate( -88.0f, -50.0f, 0.0f ));
	baum20Transform->addChild( baumTransform.get() );

	mainTransform->addChild( baum1Transform.get() );
	mainTransform->addChild( baum2Transform.get() );
	mainTransform->addChild( baum3Transform.get() );
	mainTransform->addChild( baum4Transform.get() );
	mainTransform->addChild( baum5Transform.get() );
	mainTransform->addChild( baum6Transform.get() );
	mainTransform->addChild( baum7Transform.get() );
	mainTransform->addChild( baum8Transform.get() );
	mainTransform->addChild( baum9Transform.get() );
	mainTransform->addChild( baum10Transform.get() );
	mainTransform->addChild( baum11Transform.get() );
	mainTransform->addChild( baum12Transform.get() );
	mainTransform->addChild( baum13Transform.get() );
	mainTransform->addChild( baum14Transform.get() );
	mainTransform->addChild( baum15Transform.get() );
	mainTransform->addChild( baum16Transform.get() );
	mainTransform->addChild( baum17Transform.get() );
	mainTransform->addChild( baum18Transform.get() );
	mainTransform->addChild( baum19Transform.get() );
	mainTransform->addChild( baum20Transform.get() );
}

/** Main-Methode **/
int main( int argc, char** argv ){

	/** Pfade erstellen **/
	initResources();

	/** Einstellungen **/
	startEinstellungen();

	/** Objekte und Gruppen erstellen
	Zum beobachten ob tasten gedrueckt wurden / Speichern von SceneGraph Objekten **/
    sceneHandler = new SceneGraphHandler();
	/** Zum beobachten welche tasten gedrueckt wurden **/
	eventHandler = new MyEventHandler(sceneHandler);

	erstelleObjecte();
	erstelleGruppen();
	transformiereGruppen();
	erstelleBaeume();

	/** Zusammenfuehren der Transformationen **/
	mainTransform->addChild( terrainTransform.get() );
	mainTransform->addChild( hausTransform.get() );
	mainTransform->addChild( brunnenTransform.get() );
	mainTransform->getOrCreateStateSet()->setMode(GL_RESCALE_NORMAL, StateAttribute::ON);

	/** Sphere zum schauen wo das Licht wirklich sitzt **/
	ref_ptr<ShapeDrawable> mysphere = new ShapeDrawable;
	mysphere->setShape(new Sphere(Vec3(-200.0,-100.0,200.0),50.0));

	ref_ptr<Geode> sphereGeode = new Geode;
    sphereGeode->addDrawable( mysphere.get() );

	/** Erstellen des Licht-Setups **/
	erstelleLicht();

	/** Scene-Handler setzen **/
	sceneHandler->lightGroup->addChild( mainTransform.get() );
	sceneHandler->lightGroup->addChild( sceneHandler->lichtSonne.get() );
	sceneHandler->lightGroup->addChild( sceneHandler->lichtSonne2.get() );
	sceneHandler->lightGroup->addChild( sceneHandler->lichtMond.get() );
	sceneHandler->lightGroup->addChild( sceneHandler-> lichtWohnzimmer.get());
	sceneHandler->lightGroup->addChild( sceneHandler-> lichtKueche.get());
	sceneHandler->lightGroup->addChild( sceneHandler-> lichtSchlafzimmer.get());
	sceneHandler->lightGroup->addChild( sceneHandler-> lichtFlur.get());
	sceneHandler->lightGroup->addChild( sceneHandler-> lichtBad.get());
	sceneHandler->lichtSonne->setStateSetModes(*sceneHandler->lightGroup->getOrCreateStateSet(), StateAttribute::ON );
	sceneHandler->lichtSonne2->setStateSetModes(*sceneHandler->lightGroup->getOrCreateStateSet(), StateAttribute::ON );
	sceneHandler->lichtMond->setStateSetModes(*sceneHandler->lightGroup->getOrCreateStateSet(), StateAttribute::OFF );
	sceneHandler->lichtWohnzimmer->setStateSetModes(*sceneHandler->lightGroup->getOrCreateStateSet(), StateAttribute::OFF);
	sceneHandler->lichtKueche->setStateSetModes(*sceneHandler->lightGroup->getOrCreateStateSet(), StateAttribute::OFF);
	sceneHandler->lichtSchlafzimmer->setStateSetModes(*sceneHandler->lightGroup->getOrCreateStateSet(), StateAttribute::OFF);
	sceneHandler->lichtFlur->setStateSetModes(*sceneHandler->lightGroup->getOrCreateStateSet(), StateAttribute::OFF);
	sceneHandler->lichtBad->setStateSetModes(*sceneHandler->lightGroup->getOrCreateStateSet(), StateAttribute::OFF);
	
	/** Erstellen der SkyBox **/
	erstelleSkyBox();

    sceneHandler->skyGroup->addChild( sceneHandler->lightGroup.get() );
    sceneHandler->skyGroup->addChild( sceneHandler->skyBoxTagTransform.get() );
	sceneHandler->skyGroup->addChild( &sceneHandler->getHud().getRoot() );

	/** Die Scene mit besonderem Handler fuer automatische Animationnen **/
	ref_ptr<MatrixTransform> cowTransform = new MatrixTransform();
	cowTransform->addChild( cowOBJ.get() );
	cowTransform->setUpdateCallback(new UpdateScenePosCallback(sceneHandler));

	ref_ptr<MatrixTransform> rootTransform = new MatrixTransform();
	rootTransform->addChild(cowTransform.get());
	rootTransform->addChild(sceneHandler->skyGroup);

	/** Optimieren der Szene fuer bessere Leistung **/
	osgUtil::Optimizer optimizer;
	
	/** optimieren der Texturen **/
	Texture::getTextureObjectManager(0)->setMaxTexturePoolSize( 128000 );
	osgDB::DatabasePager* pager = sceneHandler->viewer.getDatabasePager();
	pager->setDoPreCompile( true );
	pager->setTargetMaximumNumberOfPageLOD( 100 );

	sceneHandler->viewer.addEventHandler(eventHandler);

	/** Auf der Taste S gibt es eine Analyse des Models mit FPS anzeige und weiteren Infos **/
	sceneHandler->viewer.addEventHandler( new osgViewer::StatsHandler );

	/** Einstellen des Views und Starten der Szene **/
    sceneHandler->viewer.setSceneData( rootTransform.get() );
	if(fenster){
		sceneHandler->viewer.setUpViewInWindow( 100, 100, breite, hoehe );
	}
	sceneHandler->setEyeX(sceneHandler->getEyeX());
	sceneHandler->viewer.getCamera()->setAllowEventFocus(false);
    return sceneHandler->viewer.run();
}
